//Copyright 2010 Balazs Keri, Imre Keri
//
//This file is part of FireworksCreator.
//
//FireworksCreator is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.
//
//FireworksCreator is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with FireworksCreator.  If not, see <http://www.gnu.org/licenses/>.
//
#include "particle.h"
#include <cmath>
#include <osg/GL>
#include <osg/Notify>
#include <model/particletemplate.h>
#include "particlesystem.h"
#include "controller.h"
//
namespace
{
const float cosPI3=std::cos(osg::PI/3.0f);
const float sinPI3=std::sin(osg::PI/3.0f);
const float hex_texcoord_x1=0.5f+0.5f*cosPI3;
const float hex_texcoord_x2=0.5f-0.5f*cosPI3;
const float hex_texcoord_y1=0.5f+0.5f*sinPI3;
const float hex_texcoord_y2=0.5f-0.5f*sinPI3;
const float PI43=osg::PI*4.0f/3.0f;
//
const float gravitation_z=9.80665f;
//
const float air_viscosity=1.8e-5f;
const float air_density=1.2929f;
const float ff_coeff_B=0.2f*osg::PI*air_density;
const float ff_coeff_A=6*osg::PI*air_viscosity;
}
//
namespace player
{
using namespace model;
Particle::Particle()
:
   color_evaluator(0),
   alpha_evaluator(0),
   size_evaluator(0),
   radius_evaluator(0),
   alive(true),
   mustdie(false),
   t0(0),
   current_color(0,0,0,0),
   current_alpha(0),
   current_size(0),
   current_radius(0),
   current_mass(0),
   current_massinv(0),
   life_time(0),
   position(0,0,0),
   prev_position(0,0,0),
   velocity(0,0,0),
   angle(0,0,0),
   prev_angle(0,0,0),
   angul_arvel(0,0,0),
   current_tile(-1),
   s_coord(0.0f),
   t_coord(0.0f),
   system(0)
{
}
Particle::Particle(ParticleSystem *p_particle_system,unsigned int p_color_control_point_max,unsigned int p_alpha_control_point_max,unsigned int p_size_control_point_max,unsigned int p_mass_control_point_max,unsigned int p_emitter_evaluator_count)
:
   color_evaluator(p_color_control_point_max),
   alpha_evaluator(p_alpha_control_point_max),
   size_evaluator(p_size_control_point_max),
   radius_evaluator(p_mass_control_point_max),
   alive(true),
   mustdie(false),
   t0(0),
   current_color(0,0,0,0),
   current_alpha(0),
   current_size(0),
   current_radius(0),
   current_mass(0),
   current_massinv(0),
   life_time(0),
   position(0,0,0),
   prev_position(0,0,0),
   velocity(0,0,0),
   angle(0,0,0),
   prev_angle(0,0,0),
   angul_arvel(0,0,0),
   current_tile(-1),
   s_coord(0.0f),
   t_coord(0.0f),
   emitter_evaluators(p_emitter_evaluator_count),
   system(p_particle_system)
{
}
Particle::Particle(const Particle &p_other)
:
   color_evaluator(p_other.color_evaluator),
   alpha_evaluator(p_other.alpha_evaluator),
   size_evaluator(p_other.size_evaluator),
   radius_evaluator(p_other.radius_evaluator),
   alive(p_other.alive),
   mustdie(p_other.mustdie),
   t0(p_other.t0),
   current_color(p_other.current_color),
   current_alpha(p_other.current_alpha),
   current_size(p_other.current_size),
   current_radius(p_other.current_radius),
   current_mass(p_other.current_mass),
   current_massinv(p_other.current_massinv),
   life_time(p_other.life_time),
   position(p_other.position),
   prev_position(p_other.prev_position),
   velocity(p_other.velocity),
   angle(p_other.angle),
   prev_angle(p_other.prev_angle),
   angul_arvel(p_other.angul_arvel),
   current_tile(p_other.current_tile),
   s_coord(p_other.s_coord),
   t_coord(p_other.t_coord),
   emitter_evaluators(p_other.emitter_evaluators),
   system(p_other.system)
{
}
Particle &Particle::operator=(const Particle &p_other)
{
   if(&p_other!=this)
   {
      color_evaluator=p_other.color_evaluator;
      alpha_evaluator=p_other.alpha_evaluator;
      size_evaluator=p_other.size_evaluator;
      radius_evaluator=p_other.radius_evaluator;
      alive=p_other.alive;
      mustdie=p_other.mustdie;
      t0=p_other.t0;
      current_color=p_other.current_color;
      current_alpha=p_other.current_alpha;
      current_size=p_other.current_size;
      current_radius=p_other.current_radius;
      current_mass=p_other.current_mass;
      current_massinv=p_other.current_massinv;
      life_time=p_other.life_time;
      position=p_other.position;
      prev_position=p_other.prev_position;
      velocity=p_other.velocity;
      angle=p_other.angle;
      prev_angle=p_other.prev_angle;
      angul_arvel=p_other.angul_arvel;
      current_tile=p_other.current_tile;
      s_coord=p_other.s_coord;
      t_coord=p_other.t_coord;
      emitter_evaluators=p_other.emitter_evaluators;
      system=p_other.system;
   }
   return *this;
}
void Particle::start()
{
   alive=true;
   mustdie=false;
   current_radius=0;
   current_mass=0;
   current_massinv=0;
   prev_position=osg::Vec3(0,0,0);
   prev_angle=osg::Vec3(0,0,0);
   t0=0;
   //
   if(system->get_particles_visible())
   {
      current_size=0;
      current_alpha=0;
      current_tile=-1;
      s_coord=0.0f;
      t_coord=0.0f;
      color_evaluator.start(current_color);
      alpha_evaluator.start(current_alpha);
      size_evaluator.start(current_size);
   }
   radius_evaluator.start(current_radius);
   for(EmitterEvaluatorVector::iterator eei=emitter_evaluators.begin();eei!=emitter_evaluators.end();++eei)
      (*eei).start();
}
bool Particle::update(double p_dt)
{
   // this method should return false when the particle dies;
   // so, if we were instructed to die, do it now and return.
   if(mustdie)
   {
      alive=false;
      return false;
   }
   //
   // compute our normalized age.
   double x=t0/life_time;
   //
   t0 += p_dt;
   //
   // if our age is over the lifetime limit, then die and return.
   if(x>1)
   {
      //alive = false;
      //return false;
      //make it to have a last update at exactly at end
      x=1;
      t0=life_time;
      mustdie=true;
   }
   //
   if(system->get_particles_visible())
   {
      //Compute the current texture tile based on our normalized age
      int tile=static_cast<int>((system->get_particle_tile_speed_mode()==ParticleTemplate::tile_speed_relative?x:t0)*system->get_particle_num_tiles());
      //If the current texture tile is different from previous, then compute new texture coords
      if(tile!=current_tile)
      {
         current_tile=tile;
         s_coord=system->get_particle_s_tile()*(current_tile % system->get_particle_tile_s());
         t_coord=1.0-system->get_particle_t_tile()*((current_tile/system->get_particle_tile_s())%system->get_particle_tile_t());
         //osg::notify(osg::NOTICE)<<this<<" setting tex coords "<<_s_coord<<" "<<_t_coord<<std::endl;
      }
      // compute the current values for color, alpha, size, radius
      color_evaluator.update(current_color,t0);
      alpha_evaluator.update(current_alpha,t0);
      size_evaluator.update(current_size,t0);
   }
   //
   float previous_radius=current_radius;
   radius_evaluator.update(current_radius,t0);
   // compute new mass if needed
   if(previous_radius!=current_radius)
   {
      current_mass=current_radius*current_radius*current_radius*PI43*system->get_particle_density();
      if(current_mass!=0)
         current_massinv=1.0f/current_mass;
   }
   // update velocity
   //gravitation
   if(system->get_gravitation_enabled())
      velocity.z()-=gravitation_z*p_dt;
   //FluidFriction
   if(system->get_friction_enabled()&&current_mass>0)
   {
      float r=current_radius;
      osg::Vec3 v=velocity-system->get_controller()->get_wind();
      float vm=v.normalize();
      float R=ff_coeff_A*r*vm+ff_coeff_B*r*r*vm*vm;
      osg::Vec3 Fr(-R*v.x(),-R*v.y(),-R*v.z());
      osg::Vec3 dv=Fr*current_massinv*p_dt;
      float dvl=dv.length();
      if(dvl>vm)
         dv*=vm/dvl;
      velocity+=dv;
   }
   // update position
   prev_position=position;
   position+=velocity*p_dt;
   // update angle
   prev_angle=angle;
   angle+=angul_arvel*p_dt;
   if(angle.x()>osg::PI*2||angle.x()<-osg::PI*2) angle.x()=fmod(float(angle.x()),float(osg::PI*2));
   if(angle.y()>osg::PI*2||angle.y()<-osg::PI*2) angle.y()=fmod(float(angle.y()),float(osg::PI*2));
   if(angle.z()>osg::PI*2||angle.z()<-osg::PI*2) angle.z()=fmod(float(angle.z()),float(osg::PI*2));
   // trigger emitters
   for(EmitterEvaluatorVector::iterator eei=emitter_evaluators.begin();eei!=emitter_evaluators.end();++eei)
      (*eei).update(this);
   //
   return true;
}
void Particle::set_emitter_evaluator(unsigned int p_i,const EmitterEvaluator &p_emitter_evaluator)
{
   Q_ASSERT(p_i<emitter_evaluators.size());
   emitter_evaluators[p_i]=p_emitter_evaluator;
}
void Particle::render(const osg::Vec3 &p_xpos,const osg::Vec3 &p_px,const osg::Vec3 &p_py,float p_scale) const
{
   osg::Vec3 p1(p_px*current_size*p_scale);
   osg::Vec3 p2(p_py*current_size*p_scale);
   float const s_tile=system->get_particle_s_tile();
   float const t_tile=system->get_particle_t_tile();
   //
   glColor4f(current_color.r(),current_color.g(),current_color.b(),current_color.a()*current_alpha);
   switch(system->get_particle_shape())
   {
   case ParticleTemplate::shape_point:
      glVertex3f(p_xpos.x(),p_xpos.y(),p_xpos.z());
      break;
   case ParticleTemplate::shape_quad:
      glTexCoord2f(s_coord,t_coord);
      glVertex3fv((p_xpos-(p1+p2)).ptr());
      glTexCoord2f(s_coord+s_tile,t_coord);
      glVertex3fv((p_xpos+(p1-p2)).ptr());
      glTexCoord2f(s_coord+s_tile,t_coord+t_tile);
      glVertex3fv((p_xpos+(p1+p2)).ptr());
      glTexCoord2f(s_coord, t_coord+t_tile);
      glVertex3fv((p_xpos-(p1-p2)).ptr());
      break;
   case ParticleTemplate::shape_quad_trianglestrip:
      glPushMatrix();
      glTranslatef(p_xpos.x(),p_xpos.y(),p_xpos.z());
      // we must glBegin() and glEnd() here, because each particle is a single strip
      glBegin(GL_TRIANGLE_STRIP);
      glTexCoord2f(s_coord+s_tile,t_coord+t_tile);
      glVertex3fv((p1+p2).ptr());
      glTexCoord2f(s_coord,t_coord+t_tile);
      glVertex3fv((-p1+p2).ptr());
      glTexCoord2f(s_coord+s_tile,t_coord);
      glVertex3fv((p1-p2).ptr());
      glTexCoord2f(s_coord,t_coord);
      glVertex3fv((-p1-p2).ptr());
      glEnd();
      glPopMatrix();
      break;
   case ParticleTemplate::shape_hexagon:
      glPushMatrix();
      glTranslatef(p_xpos.x(),p_xpos.y(),p_xpos.z());
      // we must glBegin() and glEnd() here, because each particle is a single fan
      glBegin(GL_TRIANGLE_FAN);
      glTexCoord2f(s_coord+s_tile*0.5f,t_coord+t_tile*0.5f);
      glVertex3f(0,0,0);
      glTexCoord2f(s_coord+s_tile*hex_texcoord_x1,t_coord+t_tile*hex_texcoord_y1);
      glVertex3fv((p1*cosPI3+p2*sinPI3).ptr());
      glTexCoord2f(s_coord+s_tile*hex_texcoord_x2,t_coord+t_tile*hex_texcoord_y1);
      glVertex3fv((-p1*cosPI3+p2*sinPI3).ptr());
      glTexCoord2f(s_coord,t_coord+t_tile*0.5f);
      glVertex3fv((-p1).ptr());
      glTexCoord2f(s_coord+s_tile*hex_texcoord_x2,t_coord+t_tile*hex_texcoord_y2);
      glVertex3fv((-p1*cosPI3-p2*sinPI3).ptr());
      glTexCoord2f(s_coord+s_tile*hex_texcoord_x1,t_coord+t_tile*hex_texcoord_y2);
      glVertex3fv((p1*cosPI3-p2*sinPI3).ptr());
      glTexCoord2f(s_coord+s_tile,t_coord+t_tile*0.5f);
      glVertex3fv((p1).ptr());
      glTexCoord2f(s_coord+s_tile*hex_texcoord_x1,t_coord+t_tile*hex_texcoord_y1);
      glVertex3fv((p1*cosPI3+p2*sinPI3).ptr());
      glEnd();
      glPopMatrix();
      break;
   default:
      osg::notify(osg::WARN)<<"Invalid shape for particles\n";
   }
}
void Particle::begin_render() const
{
   switch(system->get_particle_shape())
   {
   case ParticleTemplate::shape_point:
      glBegin(GL_POINTS);
      break;
   case ParticleTemplate::shape_quad:
      glBegin(GL_QUADS);
      break;
   default:
      break;
   }
}

void Particle::end_render() const
{
   switch(system->get_particle_shape())
   {
   case ParticleTemplate::shape_point:
   case ParticleTemplate::shape_quad:
      glEnd();
      break;
   default:
      break;
   }
}
}
